/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.sparql.core ;

import java.util.Objects;

import org.apache.jena.atlas.lib.Lib ;
import org.apache.jena.query.Query ;
import org.apache.jena.query.QueryVisitor ;
import org.apache.jena.sparql.util.NodeIsomorphismMap ;

// Two queries comparison

public class QueryCompare implements QueryVisitor
{
    private Query   query2 ;
    private boolean result = true ;
    static public boolean PrintMessages = false ;

    public static boolean equals(Query query1, Query query2) {
        if ( query1 == query2 )
            return true;

        query1.ensureResultVars();
        query2.ensureResultVars();
        QueryCompare visitor = new QueryCompare(query1);
        try {
            query2.visit(visitor);
        } catch (ComparisonException ex) {
            return false;
        }
        return visitor.isTheSame();
    }

    public QueryCompare(Query query2) {
        this.query2 = query2;
    }

    @Override
    public void startVisit(Query query1) {}

    @Override
    public void visitResultForm(Query query1) {
        check("Query result form", query1.queryType() == query2.queryType());
    }

    @Override
    public void visitPrologue(Prologue query1) {
        check("Prefixes/Base", query1.samePrologue(query2));
    }

    @Override
    public void visitSelectResultForm(Query query1) {
        check("Not both SELECT queries", query2.isSelectType());
        check("DISTINCT modifier", query1.isDistinct() == query2.isDistinct());
        check("REDUCED modifier", query1.isReduced() == query2.isReduced());
        check("SELECT *", query1.isQueryResultStar() == query2.isQueryResultStar());
        check("Result variables", query1.getProject(), query2.getProject());
    }

    @Override
    public void visitConstructResultForm(Query query1) {
        check("Not both CONSTRUCT queries", query2.isConstructType());
        check("CONSTRUCT templates", query1.getConstructTemplate().equalIso(query2.getConstructTemplate(), new NodeIsomorphismMap()));
    }

    @Override
    public void visitDescribeResultForm(Query query1) {
        check("Not both DESCRIBE queries", query2.isDescribeType());
        check("Result variables", query1.getResultVars(), query2.getResultVars());
        check("Result URIs", query1.getResultURIs(), query2.getResultURIs());

    }

    @Override
    public void visitAskResultForm(Query query1) {
        check("Not both ASK queries", query2.isAskType());
    }

    @Override
    public void visitJsonResultForm(Query query) {
        check("Not both JSON queries", query2.isJsonType());
        check("Json mapping", query.getJsonMapping(), query2.getJsonMapping());
    }

    @Override
    public void visitDatasetDecl(Query query1) {
        boolean b1 = Lib.equalsListAsSet(query1.getGraphURIs(), query2.getGraphURIs());
        check("Default graph URIs", b1);
        boolean b2 = Lib.equalsListAsSet(query1.getNamedGraphURIs(), query2.getNamedGraphURIs());
        check("Named graph URIs", b2);
    }

    @Override
    public void visitQueryPattern(Query query1) {
        if ( query1.getQueryPattern() == null && query2.getQueryPattern() == null )
            return;

        if ( query1.getQueryPattern() == null )
            throw new ComparisonException("Missing pattern");
        if ( query2.getQueryPattern() == null )
            throw new ComparisonException("Missing pattern");

        // The checking for patterns (elements) involves a potential
        // remapping of system-allocated variable names.
        // Assumes blank node variables only appear in patterns.
        check("Pattern", query1.getQueryPattern().equalTo(query2.getQueryPattern(), new NodeIsomorphismMap()));
    }

    @Override
    public void visitGroupBy(Query query1) {
        check("GROUP BY", query1.getGroupBy(), query2.getGroupBy());
    }

    @Override
    public void visitHaving(Query query1) {
        check("HAVING", query1.getHavingExprs(), query2.getHavingExprs());
    }

    @Override
    public void visitLimit(Query query1) {
        check("LIMIT", query1.getLimit() == query2.getLimit());
    }

    @Override
    public void visitOrderBy(Query query1) {
        check("ORDER BY", query1.getOrderBy(), query2.getOrderBy());
    }

    @Override
    public void visitOffset(Query query1) {
        check("OFFSET", query1.getOffset() == query2.getOffset());
    }

    @Override
    public void visitValues(Query query1) {
        // Must be same order for now.
        check("VALUES/variables", query1.getValuesVariables(), query2.getValuesVariables());
        check("VALUES/values", query1.getValuesData(), query2.getValuesData());
    }

    @Override
    public void finishVisit(Query query1) {}

    private void check(String msg, Object obj1, Object obj2) {
        check(msg, Objects.equals(obj1, obj2));
    }

    private void check(String msg, boolean b) {
        if ( !b ) {
            if ( PrintMessages && msg != null )
                System.out.println("Different: " + msg);
            result = false;
            throw new ComparisonException(msg);
        }
    }

    public boolean isTheSame() { return result ; }
}
